package com.tiancheng.trade.payment.task.umsshop;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.tiancheng.trade.commom.core.enums.PaymentChannelEnum;
import com.tiancheng.trade.commom.core.enums.SettleStatusEnum;
import com.tiancheng.trade.commom.core.exception.Asserts;
import com.tiancheng.trade.commom.core.exception.BusinessException;
import com.tiancheng.trade.commom.core.exception.error.BusinessErrorInfoEnum;
import com.tiancheng.trade.commom.core.utils.JsonUtil;
import com.tiancheng.trade.commom.core.utils.DateTimeUtil;
import com.tiancheng.trade.payment.bo.SettleFlowDetailMerchantBO;
import com.tiancheng.trade.payment.channel.umsshop.UmsShopAllConfig;
import com.tiancheng.trade.payment.channel.umsshop.UmsShopProperties;
import com.tiancheng.trade.payment.channel.umsshop.UmsShopSettleClient;
import com.tiancheng.trade.payment.channel.umsshop.request.UmsShopProcessWithdrawReq;
import com.tiancheng.trade.payment.channel.umsshop.request.UmsShopWithdrawStatusQueryReq;
import com.tiancheng.trade.payment.channel.umsshop.response.UmsShopProcessWithdrawRes;
import com.tiancheng.trade.payment.channel.umsshop.response.UmsShopWithdrawMerchantBalanceQueryRes;
import com.tiancheng.trade.payment.channel.umsshop.response.UmsShopWithdrawStatusQueryRes;
import com.tiancheng.trade.payment.enums.WithdrawalStatusEnum;
import com.tiancheng.trade.payment.model.*;
import com.tiancheng.trade.payment.service.*;
import com.tiancheng.trade.payment.util.helper.PaymentIdHelper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author: kellen
 * @Description: 银联商务新购物车结算任务
 * @Date: create in 2025/03/11 09:26
 */
@Slf4j
@Component
public class UmsShopSettleTask {
    /**
     * 单个文件明细数不超过1万条
     */
    private static final int FILE_MAX = 9000;
    private static final String LOST_RULE = "缺少清分规则";
    private static final String LOST_MERCHANT = "缺少商户信息或商户企业用户号";
    private static final String LESS_APPR_AMOUNT = "划付轧差为负";
    private static final String LESS_DIST_AMOUNT = "分账轧差为负";
    private static final String SETTLE_ERROR = "ERROR";
    private static final String SETTLE_ING = "ING";
    private static final String SETTLE_SUCCESS = "SUCCESS";
    // 回盘文件查找判断时间
    public static final String SETTLE_TIME = "05:00:00";

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private UmsShopAllConfig umsShopAllConfig;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private PaymentFlowDetailSettleService paymentFlowDetailSettleService;
    @Resource
    private IPaymentFlowDetailSettleDetailService paymentFlowDetailSettleDetailService;
    @Resource
    private UmsShopSettleClient umsShopSettleClient;
    @Resource
    private IRefundSettleDetailService refundSettleDetailService;
    @Resource
    private IRefundSettleService refundSettleService;
    @Resource
    private IWithdrawalService withdrawalService;


    @Scheduled(cron = "0 0 15 * * ?")
    public void doSettle() {
        log.info("银联商务新购物车-> 自动清分任务开始执行：{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        // 同时刻不允许有多个任务处理相同数据
        RLock lock = redissonClient.getLock("UMS_SHOP_SETTLE_LOCK");
        boolean isLock;
        try {
            isLock = lock.tryLock(30L, TimeUnit.MINUTES);
            if (!isLock) {
                log.error("银联商务新购物车-> 自动清分任务执行结果：锁失败！");
                return;
            }
            // 1、查询等待清分商户信息
            final List<SettleFlowDetailMerchantBO> detailMerchantBOS = paymentFlowDetailSettleService.distinctSettleMerchant(LocalDate.now(), PaymentChannelEnum.UMS_SHOP.getCode());
            if (detailMerchantBOS.isEmpty()) {
                log.info("银联商务新购物车-> 自动清分任务执行结果：无应清分数据！");
                return;
            }

            // 平台收款子商户
            /*final Result<List<MerchantChannelResponseDTO>> orgSubMerchants = merchantFeignService.queryOrgSubMerchant(detailMerchantBOS.stream().map(SettleFlowDetailMerchantBO::getOrgPaymentChannelId).collect(Collectors.toList()));
            if (orgSubMerchants.getData().size() < 1) {
                log.error("银联商务新购物车-> 自动清分任务执行结果：缺少平台子商户！");
                return;
            }
            final Map<Long, MerchantChannelResponseDTO> orgSubMerchantsMap = orgSubMerchants.getData().stream().collect(Collectors.toMap(MerchantChannelResponseDTO::getOrgPaymentChannelId, Function.identity(), (k1, k2) -> k2));*/

            // 2、查询等待提现的明细项
            // 所有-支付结算记录
            List<PaymentFlowDetailSettle> allSettles = new ArrayList<>();
            // 按商户聚合-支付结算明细
            Map<Long, List<PaymentFlowDetailSettleDetail>> paySettlesMap = new HashMap<>();
            queryPayOrderSettleDetails(allSettles, paySettlesMap);
            if (CollectionUtils.isEmpty(allSettles) || MapUtils.isEmpty(paySettlesMap)) {
                log.info("银银联商务新购物车-> 当日无提现订单！");
                return;
            }
            // 3、查询等待轧差的退款结算单
            // 所有-退款结算记录
            // 按商户聚合-退款结算明细
            Map<Long, List<RefundSettleDetail>> refundSettlesMap = queryRefundOrderSettleDetails();

            // 4、根据支付需要清分的金额、退款轧差的金额，校验银联侧的金额
            for (Map.Entry<Long, List<PaymentFlowDetailSettleDetail>> payMerchantEntry : paySettlesMap.entrySet()) {
                log.info(">>>>>>>商户清分订单处理开始，商户id：{}，商户名：{} <<<<<<<", payMerchantEntry.getKey(), payMerchantEntry.getValue().get(0).getMerchantName());
                Optional<SettleFlowDetailMerchantBO> first = detailMerchantBOS.stream().filter(m -> m.getMerchantRegId().equals(payMerchantEntry.getKey())).findFirst();
                if (first.isEmpty()) {
                    // 没有查询到对应商户的信息，目前先打印日志吧，后面看要不要设置结算单的失败状态
                    log.info("商户：{} 没有查询到对应的信息，提现流程失败！", payMerchantEntry.getKey());
                    return;
                }
                // 提现单号
                String withdrawalFlowId = PaymentIdHelper.getWithdrawalFlowId(payMerchantEntry.getKey().toString());
                if (CollectionUtils.isEmpty(payMerchantEntry.getValue())) {
                    log.warn("商户无等待清分的支付明细结算单！商户id:{}", payMerchantEntry.getKey());
                    return;
                }
                // 总的要结算的金额
                boolean hashRefundOrder = false;
                int settleSumAmount = payMerchantEntry.getValue().stream().mapToInt(PaymentFlowDetailSettleDetail::getAmount).sum();
                log.info("-------商户当日要应清分支付订单总额：{}", settleSumAmount);
                if (MapUtils.isNotEmpty(refundSettlesMap) && refundSettlesMap.containsKey(payMerchantEntry.getKey())
                        && CollectionUtils.isNotEmpty(refundSettlesMap.get(payMerchantEntry.getKey()))) {
                    // 如果有退款等待轧差的结算单，计算金额
                    int refundSum = refundSettlesMap.get(payMerchantEntry.getKey()).stream().mapToInt(RefundSettleDetail::getAmount).sum();
                    log.info("-------商户当日要轧差的退款总额：{}", refundSum);
                    settleSumAmount -= Math.abs(refundSum);
                    hashRefundOrder = true;
                }
                log.info("-------商户当日最终要提现总额：{}", settleSumAmount);

                boolean success = true;
                String ssn = "";
                // 如果待清分金额大于0，调用银联的提现相关接口，否则直接设置提现成功
                if (settleSumAmount > 0) {
                    // 查询银联侧商户余额
                    PaymentFlowDetailSettle paymentFlowDetailSettle = allSettles.stream().filter(s -> s.getSettleFlowId().equals(payMerchantEntry.getValue().get(0).getSettleFlowId())).findFirst().get();

                    final UmsShopProperties umsMopProperties = umsShopAllConfig.getByOrgPayChannelId(paymentFlowDetailSettle.getOrgPaymentChannelId());
                    UmsShopWithdrawMerchantBalanceQueryRes subMerchantBalance = umsShopSettleClient.queryWithdrawBalance(umsMopProperties, payMerchantEntry.getValue().get(0).getSettleMerchantId());
                    Asserts.isTrue(subMerchantBalance.isOK(), "商户余额查询失败！");
                    // 待提现金额
                    Integer tzWithdrawAmt = subMerchantBalance.getTzWithdrawAmt();
                    if (settleSumAmount > tzWithdrawAmt) {
                        // 如果余额不够，不进行提现
                        log.info("商户提现余额不足，当日不处理！计划提现金额：{}，账户余额：{}", settleSumAmount, tzWithdrawAmt);
                        continue;
                    }
                    // 提现请求参数
                    UmsShopProcessWithdrawReq params = new UmsShopProcessWithdrawReq();
                    params.setMchntNo(payMerchantEntry.getValue().get(0).getSettleMerchantId());
                    params.setSysOrderId(withdrawalFlowId);
                    params.setWithdrawType("0");
                    params.setWithdrawAmt(String.valueOf(settleSumAmount));
                    params.setFeeRatio("0");
                    params.setSubsFlag("0");
                    params.setMinFee("0");
                    params.setRemark(getRemark(payMerchantEntry.getValue().get(0).getMerchantName()));
                    params.setPostscript(params.getRemark());
                    UmsShopProcessWithdrawRes withdrawRes = umsShopSettleClient.processWithdraw(umsMopProperties, params);
                    log.info("提现接口返回：{}", JsonUtil.toJsonString(withdrawRes));
                    success = withdrawRes.isOK();
                    ssn = withdrawRes.getSsn();
                }
                // 提现成功
                PaymentFlowDetailSettleDetail pfdsd = new PaymentFlowDetailSettleDetail();
                pfdsd.setSettleStatus(success ? SettleStatusEnum.ING.getCode() : SettleStatusEnum.FAIL.getCode());
                pfdsd.setWithdrawalId(withdrawalFlowId);
                paymentFlowDetailSettleDetailService.update(pfdsd, new LambdaQueryWrapper<PaymentFlowDetailSettleDetail>()
                        .in(PaymentFlowDetailSettleDetail::getId, payMerchantEntry.getValue().stream().map(PaymentFlowDetailSettleDetail::getId).collect(Collectors.toSet())));

                PaymentFlowDetailSettle updatePaySettle = new PaymentFlowDetailSettle();
                updatePaySettle.setSettleStatus(success ? SettleStatusEnum.ING.getCode() : SettleStatusEnum.FAIL.getCode());
                paymentFlowDetailSettleService.update(updatePaySettle, new LambdaQueryWrapper<PaymentFlowDetailSettle>().in(PaymentFlowDetailSettle::getSettleFlowId, payMerchantEntry.getValue().stream().map(PaymentFlowDetailSettleDetail::getSubFlowId).collect(Collectors.toSet())));
                if (hashRefundOrder) {
                    RefundSettleDetail rsd = new RefundSettleDetail();
                    rsd.setSettleStatus(success ? SettleStatusEnum.ING.getCode() : SettleStatusEnum.FAIL.getCode());
                    rsd.setWithdrawalId(withdrawalFlowId);
                    refundSettleDetailService.update(rsd, new LambdaQueryWrapper<RefundSettleDetail>()
                            .in(RefundSettleDetail::getId, refundSettlesMap.get(payMerchantEntry.getKey()).stream().map(RefundSettleDetail::getId).collect(Collectors.toSet())));
                }
                // 保存提现记录
                Withdrawal withdrawal = new Withdrawal();
                withdrawal.setWithdrawalId(withdrawalFlowId);
                withdrawal.setSsn(ssn);
                withdrawal.setMerchantRegId(payMerchantEntry.getKey());
                withdrawal.setMerchantCode(payMerchantEntry.getValue().get(0).getMerchantCode());
                withdrawal.setMerchantName(payMerchantEntry.getValue().get(0).getMerchantName());
                withdrawal.setSubMerchantId(payMerchantEntry.getValue().get(0).getSubMerchantId());
                withdrawal.setEnterpriseCode(payMerchantEntry.getValue().get(0).getSettleMerchantId());
                withdrawal.setAmount(settleSumAmount);
                withdrawal.setFee(0);
                withdrawal.setWithdrawalStatus(success ? WithdrawalStatusEnum.ING.getCode() : WithdrawalStatusEnum.FAIL.getCode());
                withdrawal.setOrgPaymentChannelId(first.get().getOrgPaymentChannelId());
                withdrawal.setPaymentChannel(first.get().getPaymentChannel());
                withdrawal.setCompleteTime(LocalDateTime.now());
                withdrawal.setUpdateTime(LocalDateTime.now());
                withdrawal.setCreateTime(LocalDateTime.now());
                withdrawalService.save(withdrawal);
                if (settleSumAmount == 0) {
                    // 没有执行银商提现的提现记录直接设置为提现成功、
                    withdrawalResult(withdrawal.getWithdrawalId(), "0", DateTimeUtil.formatter(LocalDateTime.now(), DateTimeUtil.bj_pattern), WithdrawalStatusEnum.SUCCESS);
                }
            }
        } catch (Exception e) {
            log.error("银联商务新购物车-> 提现自动任务执行失败：", e);
            throw new BusinessException(BusinessErrorInfoEnum.UMS_SETTLE_ERROR, null, "银联商务新购物车-> 提现自动任务执行失败", e);
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }


    private String getRemark(String merchantName) {
        String str = StringUtils.isNotEmpty(merchantName) ? merchantName + "分账所得" : "商户分账所得";
        return str.length() >= 20 ? str.substring(0, 19) : str;
    }

    /**
     * 提现状态查询
     * 每天21-23点期间每隔30分钟执行一次
     */
    //@Scheduled(cron = "0 */30 21,22,23 * * ?")
    @Scheduled(cron = "0 */30 17,18,19 * * ?")
    public void queryWithdrawStatus() {
        List<Withdrawal> withdrawals = withdrawalService.list(new LambdaQueryWrapper<Withdrawal>().eq(Withdrawal::getWithdrawalStatus, WithdrawalStatusEnum.ING.getCode())
                .eq(Withdrawal::getPaymentChannel, "umsShop"));
        if (CollectionUtils.isEmpty(withdrawals)) {
            log.info("未查询到提现记录，返回！");
            return;
        }
        // 查询一次体现状态
        for (Withdrawal withdrawal : withdrawals) {
            final UmsShopProperties umsMopProperties = umsShopAllConfig.getByOrgPayChannelId(withdrawal.getOrgPaymentChannelId());
            UmsShopWithdrawStatusQueryReq queryReq = new UmsShopWithdrawStatusQueryReq();
            queryReq.setMchntNo(withdrawal.getEnterpriseCode());
            queryReq.setSysOrderId(withdrawal.getWithdrawalId());
            queryReq.setTransDate(DateTimeUtil.formatter(withdrawal.getCreateTime(), "yyyyMMdd"));
            UmsShopWithdrawStatusQueryRes withdrawResult = umsShopSettleClient.queryWithdrawStatus(umsMopProperties, queryReq);
            if (Objects.isNull(withdrawResult) || !withdrawResult.isOK()) {
                log.info("查询商户体现结果失败,提现记录：{}，银联返回结果：{}", withdrawal, withdrawResult);
                continue;
            }
            String status = withdrawResult.getStatus();
            if (StringUtils.isNotEmpty(status)) {
                WithdrawalStatusEnum withdrawalStatusEnum = WithdrawalStatusEnum.ING;
                switch (status) {
                    case "-1":
                        // 数据问题
                        withdrawalStatusEnum = WithdrawalStatusEnum.ING;
                        break;
                    case "4":
                    case "5":
                        // 失败
                        withdrawalStatusEnum = WithdrawalStatusEnum.FAIL;
                        break;
                    case "0":
                    case "1":
                    case "2":
                    case "6":
                        // 处理中
                        break;
                    case "3":
                        // 成功
                        withdrawalStatusEnum = WithdrawalStatusEnum.SUCCESS;
                        break;
                    default:
                        log.warn("未知的体现状态：{}", status);
                        break;
                }
                if (withdrawalStatusEnum != WithdrawalStatusEnum.ING) {
                    // 提现成功/失败
                    WithdrawalStatusEnum finalWithdrawalStatusEnum = withdrawalStatusEnum;
                    Boolean isSc = transactionTemplate.execute(transactionStatus -> {
                        try {
                            withdrawalResult(withdrawal.getWithdrawalId(), withdrawResult.getFee(), withdrawResult.getTransTime(), finalWithdrawalStatusEnum);
                            return true;
                        } catch (Exception e) {
                            log.info("执行提现结果处理逻辑异常:", e);
                            transactionStatus.setRollbackOnly();
                            throw e;
                        }
                    });
                }
            }
        }
    }

    /**
     * 提现接口处理
     *
     * @param withdrawalId              提现id
     * @param fee                       手续费
     * @param transTimeStr              提现时间：yyyy-MM-dd HH:mm:ss
     * @param finalWithdrawalStatusEnum 提现状态
     */
    private void withdrawalResult(String withdrawalId, String fee, String transTimeStr, WithdrawalStatusEnum finalWithdrawalStatusEnum) {
        // 更新提现记录状态，处理后续流程
        Withdrawal updWithdrawal = new Withdrawal();
        updWithdrawal.setWithdrawalStatus(finalWithdrawalStatusEnum.getCode());
        if (StringUtils.isNotEmpty(fee)) {
            updWithdrawal.setFee(Integer.parseInt(fee));
        }
        LocalDateTime transTime = null;
        if (StringUtils.isNotEmpty(transTimeStr)) {
            transTime = DateTimeUtil.string2LocalDateTime(transTimeStr, DateTimeUtil.bj_pattern);
            updWithdrawal.setCompleteTime(transTime);
        }
        updWithdrawal.setUpdateTime(LocalDateTime.now());
        withdrawalService.update(updWithdrawal, new LambdaQueryWrapper<Withdrawal>().eq(Withdrawal::getWithdrawalId, withdrawalId));
        paymentFlowDetailSettleService.withdrawResult(withdrawalId, finalWithdrawalStatusEnum, transTime);
    }

    /**
     * 汇总当前等待轧差的退款结算单
     */
    private Map<Long, List<RefundSettleDetail>> queryRefundOrderSettleDetails() {
        long size = 1000L; // 每批次查询起始id
        long current = 1;// 当前页
        Map<Long, List<RefundSettleDetail>> merchantRefundSettleDetailsMap = new HashMap<>();
        List<RefundSettle> list;
        do {
            // 3.1、抓取当日应清分子订单和滞留子订单，每次抓取1000条
            IPage<RefundSettle> refundSettleIPage = refundSettleService.listWaitSettles(LocalDate.now(), PaymentChannelEnum.UMS_SHOP.getCode(), current, size);
            list = refundSettleIPage.getRecords();
            if (CollectionUtils.isEmpty(list)) {
                log.info("未查询到待结算订单或待结算订单处理完毕！");
                break;
            }
            // 支付流水清分明细项
            List<RefundSettleDetail> refundSettleDetails = refundSettleDetailService.queryBySettleIds(list.stream().map(RefundSettle::getId).collect(Collectors.toSet()));
            current++;
            // 按商户分组所有的结算明细项
            if (CollectionUtils.isEmpty(refundSettleDetails)) {
                continue;
            }
            // 按商户分组所有的结算明细项
            Map<Long, List<RefundSettleDetail>> merchantSettles = refundSettleDetails.stream().collect(Collectors.groupingBy(RefundSettleDetail::getMerchantRegId));
            if (!merchantSettles.isEmpty()) {
                merchantSettles.forEach((merchantId, refundFlowDetailSettleDetails) -> {
                    if (merchantRefundSettleDetailsMap.containsKey(merchantId) && CollectionUtils.isNotEmpty(merchantRefundSettleDetailsMap.get(merchantId))) {
                        merchantRefundSettleDetailsMap.get(merchantId).addAll(refundFlowDetailSettleDetails);
                    } else {
                        merchantRefundSettleDetailsMap.put(merchantId, refundFlowDetailSettleDetails);
                    }
                });
            }
        } while (list.size() > 999);
        return merchantRefundSettleDetailsMap;
    }

    /**
     * 汇总当前等待提现的订单明细
     *
     * @param allSettles         返回的所有等待提现的订单
     * @param merchantSettlesMap 按商户维度汇总的所有等待提现的商户明细
     */
    private void queryPayOrderSettleDetails(List<PaymentFlowDetailSettle> allSettles, Map<Long, List<PaymentFlowDetailSettleDetail>> merchantSettlesMap) {
        long size = 1000L; // 每批次查询起始id
        long current = 1;// 当前页
        List<PaymentFlowDetailSettle> list;
        do {
            // 3.1、抓取当日应清分子订单和滞留子订单，每次抓取1000条
            IPage<PaymentFlowDetailSettle> flowDetailSettleIPage = paymentFlowDetailSettleService.listWaitSettles(LocalDate.now(), PaymentChannelEnum.UMS_SHOP.getCode(), current, size);
            list = flowDetailSettleIPage.getRecords();
            if (CollectionUtils.isEmpty(list)) {
                log.info("未查询到待结算订单或待结算订单处理完毕！");
                break;
            }
            // 支付流水清分明细项
            List<PaymentFlowDetailSettleDetail> flowDetailSettleDetails = paymentFlowDetailSettleDetailService.queryWaitSettleDetailsBySettleIds(list.stream().map(PaymentFlowDetailSettle::getId).collect(Collectors.toSet()));
            allSettles.addAll(list);
            current++;
            // 按商户分组所有的结算明细项
            if (CollectionUtils.isEmpty(flowDetailSettleDetails)) {
                continue;
            }
            Map<Long, List<PaymentFlowDetailSettleDetail>> merchantSettles = flowDetailSettleDetails.stream().collect(Collectors.groupingBy(PaymentFlowDetailSettleDetail::getMerchantRegId));
            if (!merchantSettles.isEmpty()) {
                merchantSettles.forEach((merchantId, paymentFlowDetailSettleDetails) -> {
                    if (merchantSettlesMap.containsKey(merchantId) && CollectionUtils.isNotEmpty(merchantSettlesMap.get(merchantId))) {
                        merchantSettlesMap.get(merchantId).addAll(paymentFlowDetailSettleDetails);
                    } else {
                        merchantSettlesMap.put(merchantId, paymentFlowDetailSettleDetails);
                    }
                });
            }
        } while (list.size() > 999);
    }

}
